Technical Note TN2088
TWAIN Data Sources for Mac OS X

目次

このテクニカルノートでは、TWAIN Data Source (DS) for Mac OS Xの実装方法について説明します。

[2003 年 6 月 19 日]



TWAIN for Mac OS X

TWAIN は、ソフトウェアアプリケーションと画像入力デバイス(データソース)の間の通信に関する標準ソフトウェアプロトコルと API (application programming interface) を定義しています。このテクニカルノートでは、読者がすでに TWAIN の仕様に精通していることを前提としています。TWAIN の完全な仕様と、TWAIN の詳細については、TWAIN ワーキンググループの Web サイトを参照してください。

Mac OS X の TWAIN の実装には、TWAIN DSM (Data Source Manager) が含まれています。このソフトウェアは、アプリケーションと TWAIN Data Source の間の対話を管理します。DSMは Mach-O フレームワークとして実装され、次の場所に格納されています。

/System/Library/Frameworks/TWAIN.framework

Mach-O ベースの Cocoa および Carbon クライアントアプリケーションは、すべてこの TWAIN.framework にリンクする必要があります。

CFM ベースのアプリケーションには、下記のディレクトリにある CFM グルーライブラリが必要です。

/System/Library/CFMSupport/TWAIN Source Manager.Shlb

TWAIN Data Source は CFM または Mach-O ベースでかまいません。ただし、バンドルとして実装する必要があります(後述の「TWAIN DS パッケージ」のセクションを参照)。

デベロッパは、TWAIN Data Source を次の場所に置く必要があります。

/Library/Image Capture/TWAIN Data Sources/

ただし、Image Capture は下記のディレクトリも探索します。

/System/Library/Image Capture/TWAIN Data Sources/

また Mac OS X 10.2 以降では、Apple Image Capture は TWAIN ソフトウェア「ブリッジ」の TWAINBridge.app(/System/Library/Image Capture/Devices/TWAINBridge.app にあります)を使用して、TWAIN Data Source にアクセスします。この TWAINBridge ソフトウェアにより、どの Image Capture クライアントでも TWAIN デバイスへのアクセスは、ネイティブのスキャナドライバで処理されているかのように透過的に行われます。

先頭に戻る



TWAIN DS パッケージ

TWAIN Data Source は、TWAIN DSM が認識できるように、新しい形式のバンドルとして以下のように配置する必要があります。

図 1 DS バンドルの配置



バンドルの詳細については、Mac OS X Bundles を参照してください。

このバンドルには、「Contents」ディレクトリとその中に「Info.plist」ファイルが含まれていなければなりません。「Contents」ディレクトリには、「Resources」ディレクトリとその中に「DeviceInfo.plist」ファイルも含まれている必要があります。これらのプロパティリストファイルの詳細については、後述の「TWAIN DS プロパティリストファイル」を参照してください。

また、バンドルのフォルダ拡張子は「.ds」にする必要があります。

先頭に戻る



TWAIN DS UI 非表示モード

TWAIN DS for Mac OS X は、もう 1 つ、UI (user interface) を表示しないオプションの操作モードをサポートする必要があります。詳細については、TWAIN の仕様を確認してください。これにより、Apple Image Capture のようなアプリケーションは、Image Capture TWAINBridge ソフトウェアを介して TWAIN DS を使用することができます。

先頭に戻る



TWAIN DS プロパティリストファイル

他の新しい形式のバンドルと同様に、TWAIN DS は情報プロパティリストファイル「Info.plist」を「Contents」フォルダに保持しています。「Info.plist」には、CFBundleIdentifierCFBundleName などのキーの標準エントリがあります。

TWAIN DS を使用するために、Image Capture には TWAIN DS と接続デバイスを結び付ける手段が必要です。デバイス関連情報を Data Source の「Info.plist」に追加すると、結び付けることができます。

FireWire デバイスの場合は、「Info.plist」に製品 ID とベンダー ID、および「スキャナ」に設定するデバイスタイプが含まれています。USB デバイスの場合は、「Info.plist」に製品 ID とベンダー ID、および「スキャナ」に設定するデバイスタイプが含まれています。製品 ID とベンダー ID は IORegistry から取得します。

デバイスを接続して、このような IORegistry 値を調べるには、IORegistryExplorer ユーティリティ(Apple Mac OS X Developer Tools に含まれています。Apple Mac OS X Developer Tools を参照)を使用するか、ターミナルから ioreg ツールを使用します(たとえば、"ioreg -lw 0 dump" と入力)。

さらに具体的にいうと、USB デバイスの場合は、IOUSBDevice というノードにプロパティが格納されていて、「idVendor」と「idProduct」という名前が付いています。FireWire デバイスの場合は、プロパティに「Vendor Identification」と「Product Identification」という名前が付けられ、IOSCSIPeripheralDeviceNub ノードに格納されています。

下記のリストは ioreg ツールからのターミナル出力で、USB デバイスのプロパティを示しています。

リスト 1. USB デバイスに関する ioreg ツールの出力

 +-o IOUSBDevice@1100000  
   | {
   |   "bDeviceSubClass" = 0
   |   "bcdDevice" = 256
   |   "USB Serial Number" = "AP94M1703PPE"
   |   "IOUserClientClass" = "IOUSBDeviceUserClient"
   |   "USB Vendor Name" = "ACME Peripherals"
   |   "IOGeneralInterest" = ("_IOServiceInterestNotifier is not serial...
   |   "idVendor" = 1452
   |   "Device Speed" = 1
   |   "sessionID" = 167599321426758
   |   "locationID" = 17825792
   |   "iManufacturer" = 1
   |   "iProduct" = 2
   |   "bDeviceProtocol" = 0
   |   "bDeviceClass" = 0
   |   "idProduct" = 291
   |   "Bus Power Available" = 250
   |   "bMaxPacketSize0" = 8
   |   "USB Address" = 2
   |   "PortNum" = 1
   |   "IOCFPlugInTypes" = {"9dc7b780-9ec0-11d4-a54f-000a27052861"="IOUSB...
   |   "bNumConfigurations" = 1
   |   "iSerialNumber" = 3
   | }

下記のリストは ioreg ツールからのターミナル出力で、FireWire デバイスのプロパティを示しています。

リスト 2. FireWire デバイスに関する ioreg ツールの出力

+-o IOSCSIPeripheralDeviceNub  
   | {
   |   "Product Identification" = "ABC 2000"
   |   "Product Revision Level" = "2.03"
   |   "IOProviderClass" = "IOSCSIProtocolServices"
   |   "CFBundleIdentifier" = "com.apple.iokit.IOSCSIArchitectureModelFam...
   |   "Vendor Identification" = "ACME Peripherals"
   |   "Protocol Characteristics" = {"Physical Interconnect Location"="Ex...
   |   "IOMatchCategory" = "SCSITaskUserClientIniter"
   |   "IOUserClientClass" = "SCSITaskUserClient"
   |   "IOClass" = "IOSCSIPeripheralDeviceNub"
   |   "IOProbeScore" = 0
   |   "Peripheral Device Type" = 3
   |   "IOCFPlugInTypes" = {"7D66678E-08A2-11D5-A1B8-0030657D052A"="IOSCS...
   |   "SCSITaskUserClient GUID" = <02e41c00000023363114ca1d>
   |   "SCSITaskDeviceCategory" = "SCSITaskUserClientDevice"
   | }
   | 

以下に示すのは、一般的なスキャナデバイスのプロパティに関する Property List Editor ユーティリティからの(同様に Mac OS X Developer Tools からの)サンプル出力に、製品およびベンダーの適当な値を追加したものです。



デバイスに関する「DeviceInfo.plist」プロパティリストファイル(バンドルの「Resource」ディレクトリにある第 2 のプロパティリストファイル)には、Image Capture TWAINBridge が TWAIN DS を使用できるようにする情報が含まれています。このファイルには、Data Source が TWAIN ID として返すものに Data Source バンドルをマッピングできるようにするエントリ「ProductNames」も含む必要があります。「DeviceInfo.plist」ファイルの内容は以下に示すようなものです。



また、「DeviceInfo.plist」は、「ButtonListener」エントリにボタンリスナープラグインコードモジュール(後述の「TWAIN DS ボタンリスナー」を参照)も含むことができます。このプラグインモジュールは、TWAINBridge によってロードされ、接続デバイスの「ボタンリスニング」を実行します。デバイスのボタンを押すと、ユーザが選択したアプリケーションが起動します。

先頭に戻る



TWAIN DS ボタンリスナー

先述の「TWAIN DS プロパティリストファイル」のセクションで述べたように、TWAIN DS では「DeviceInfo.plist」プロパティリストファイルのボタンリスナープラグインコードモジュールへの参照を指定することができます。このコードモジュールは DS の「Resources」フォルダにあり、TWAINBridge によってロードされ、デバイスのボタン押下を照会します。

ボタンリスナープラグインのサンプルコード (SampleButtonPlugin) は、アップルのデベロッパ Web サイトの SampleButtonPlugin サンプルコード に公開されています。

このプラグインコードモジュールはバンドルとして同梱されています。このバンドルファイルには、拡張子「.btn」が付いています。その配置は以下に示すとおりです。



図 1 ボタンリスナーバンドルの配置


このプラグインコードモジュールは、下記のシンボルをエクスポートする必要があります。

リスト 1. モジュールエントリポイント

_TWAINButtonPluginStart
_TWAINButtonPluginStop


TWAINButtonPluginStart エントリポイントが TWAINBridge に呼び出されて、プラグインによるボタンリスニングが開始します。そして、TWAINButtonPluginStop エントリポイントが TWAINBridge に呼び出されて、プラグインによるボタンリスニングが終了します。

これらのエントリポイントのプロトタイプは以下のとおりです。

リスト 2. モジュールエントリポイントのプロトタイプ

OSErr TWAINButtonPluginStart(UInt32* locationID,
                             UInt64* guid,
                             io_string_t twainPath,
                             ButtonPressedCallback callback);

OSErr TWAINButtonPluginStop(UInt32* locationID,
                            UInt64* guid,
                            io_string_t twainPath);



USB デバイスの場合は、TWAINButtonPluginStart エントリポイントに渡す locationID パラメータに、USB デバイスを一意的に識別する有効な値が含まれます。この場合は、guid パラメータ(GUID、すなわちグローバルで一意の ID に対応)を使用しません。

FireWire デバイスの場合は、TWAINButtonPluginStart エントリポイントに渡す guid パラメータ(GUID に対応)には、FireWire デバイスを一意に識別する有効な値が含まれます。この場合は、locationID パラメータを使用しません。

twainPath では、プラグインモジュールのファイルパスを指定します。

TWAINButtonPluginStart エントリポイントが呼び出されると、プラグインはボタンが押されたかどうかを判断するために、一般的に(タイマによって)キー押下のポーリングを開始します。ボタンが押されたとプラグインが判断した場合は、(callback パラメータを通じて TWAINButtonPluginStart エントリポイントに渡された)ボタン押下コールバックプロシージャを呼び出す必要があります。この方法でボタン押下コールバックを呼び出すと、Image Capture アプリケーションの「スキャナ環境設定」で指定したアプリケーションが起動します。

ボタン押下コールバックプロシージャのプロトタイプを以下に示します。


リスト 3. ボタン押下コールバックのプロトタイプ

typedef CALLBACK_API_C( void, ButtonPressedCallback )(OSType message,
                                                      UInt32*  locationID,
                                                      UInt64*  guid);



先頭に戻る



TWAIN DS と Carbon イベント

Mac OS 9 に対応した従来の TWAIN の実装は、WaitNextEvent ループで実行されていました。つまり、クライアントアプリケーションは、NULL イベントと、他のすべてのイベントを DS に渡すことで、TWAIN DS を積極的にポーリングしていたのです。そして、イベントを処理するか、クライアントアプリケーションに返すかを DS が判断していました。

このようなイベント処理は Mac OS X 環境には適していません。Carbon または Cocoa アプリケーションは、ユーザにイベントループを公開していないからです。旧式の WaitNextEvent アプリケーションについては、TWAIN DSM が現在のところ MSG_PROCESSEVENT をブロックし、TWAIN DS にも渡しません。

先頭に戻る



クライアントから DS へのイベントの受け渡し――従来の方法

TWAIN Specification Version 1.9 の 3-28 〜 3-31 ページでは、アプリケーションのイベントループを変更して、クライアントアプリケーションから DS にイベントを渡して、DS が適切に応答できるようにする方法と、DS から送信されたメッセージを適切にチェックする方法について説明しています。3-30 ページでは、Macintosh アプリケーションが旧式の WaitNextEvent ループでイベントをポーリングして TWAIN の接続先のソースをサポートするのに必要な一般的な変更について解説しています。

イベントを DS に送信するために、アプリケーションは通常 DG_CONTROL / DAT_EVENT / MSG_PROCESSEVENT を使用し、これらのイベントに使用する TW_EVENT データ構造の pEvent フィールドで Macintosh EventRecord を指示します。この場合は、DS が Source Manager からイベントを受信し、そのイベントが誰に帰属するかを判断します。

このようにしてイベントを DS に送信する手法は、Mac OS X TWAIN DSM によってまだサポートされていますが、好ましい手法ではありません。

先頭に戻る



クライアントから DS へのイベントの受け渡し――新しい方法

TWAIN DS は現在、Mac OS X 上のイベントごとに前述の MSG_PROCESSEVENT メッセージを使ってアプリケーションによって呼び出す代わりに、イベントを受信するために CarbonEvent ハンドラを設定する必要があります。このようなイベントハンドラは、UI (user-interface) の対話にも使用します。

Carbon イベントの詳細については、Carbon Event Manager Reference を参照してください。

つまり、TW_EVENT 構造体の pData フィールドに旧式の EventRecord がある MSG_PROCESSEVENT を使って呼び出す代わりに、TWAIN DS は以下のように Carbon API を使って Carbon Event ハンドラを設定しなければならないということです。



リスト 4. Carbon Event ハンドラの設定

InstallStandardEventHandler(GetWindowEventTarget(GetDialogWindow(gDialog)));

InstallWindowEventHandler(GetDialogWindow(gDialog),
          NewEventHandlerUPP((EventHandlerProcPtr) windowEventHandler),
          GetEventTypeCount(windowEvents), windowEvents,
          0,
          NULL);

すべての UI の対話に対して、このイベントハンドラが呼び出されます。

以下に示す詳細なコード例は、Carbon Event ハンドラの設定方法を示す Data Source サンプルコード(アップルのデベロッパ Web サイトの Data Source サンプルコードから入手可能)から抜粋したものです。



リスト 5. Carbon Eventハンドラの設定

// ------------------------------------------------------------------
//    DisplayUserInterface
// ------------------------------------------------------------------
//

void DisplayUserInterface()
{
    SInt16       savedResRefNum;
    SInt16       resRefNum = 0;
    OSStatus     status = noErr;
    CFBundleRef  selfBundleRef;
    ControlRef   pictureControlRef;

EventTypeSpec windowEvents[] = {{kEventClassWindow,kEventWindowDrawContent},
    { kEventClassWindow, kEventWindowUpdate },
    { kEventClassWindow, kEventWindowClose },
    { kEventClassWindow, kEventWindowClickDragRgn },
    { kEventClassMouse, kEventMouseDown },
    { kEventClassMouse, kEventMouseMoved } };

    selfBundleRef = 
         CFBundleGetBundleWithIdentifier(CFSTR("com.apple.sampleds"));

    if (selfBundleRef)
        resRefNum = CFBundleOpenBundleResourceMap ( selfBundleRef );

    savedResRefNum = CurResFile();
    UseResFile ( resRefNum );

    gDialog = GetNewDialog(128, nil, (WindowRef)-1);

    UseResFile ( savedResRefNum );

    ChangeWindowAttributes(GetDialogWindow(gDialog),
                           kWindowStandardHandlerAttribute |
                           kWindowCloseBoxAttribute,
                           kWindowCollapseBoxAttribute);

    GetDialogItemAsControl(gDialog, kScanButton,   &gScanButton);
    GetDialogItemAsControl(gDialog, kCancelButton, &gCancelButton);

InstallStandardEventHandler(GetWindowEventTarget(GetDialogWindow(gDialog)));
    InstallWindowEventHandler(GetDialogWindow(gDialog),
          NewEventHandlerUPP((EventHandlerProcPtr) windowEventHandler),
          GetEventTypeCount(windowEvents), windowEvents,
          0,NULL);

    DS_LogText("InstallWindowEventHandler done¥n");

    ShowWindow(GetDialogWindow(gDialog));

    status = GetDialogItemAsControl ( gDialog, 3, &pictureControlRef );
    require_noerr ( status, BAIL );

    gPicture = GetPicture(130);
    status = SetControlData ( pictureControlRef,
                              kControlPicturePart,
                              kControlPictureHandleTag,
                              sizeof ( PicHandle ),
                              &gPicture );
    require_noerr ( status, BAIL );

    DrawOneControl ( pictureControlRef );

BAIL:

    return;
}



// -------------------------------------------------------------------
//    windowEventHandler
// -------------------------------------------------------------------
//

OSStatus  windowEventHandler(EventHandlerCallRef eventHandlerCallRef,
                             EventRef            eventRef,
                             void*               userData)
{
#pragma unused (eventHandlerCallRef, userData)

    OSStatus    result = eventNotHandledErr;
    UInt32      eventClass;
    UInt32      eventKind;
    EventRecord eventRecord;

    eventClass = GetEventClass(eventRef);
    eventKind  = GetEventKind(eventRef);

    switch(eventClass)
    {
             // イベントクラスウインドウ
        case kEventClassWindow:    

            ConvertEventRefToEventRecord(eventRef,&eventRecord);

            switch(eventKind)
            {
                case kEventWindowUpdate:

                    doDrawContent(GetDialogWindow(gDialog));

                    break;

              /* etc. */
            }
    }
}



先頭に戻る



DS からクライアントへの情報の受け渡し――従来の方法

TWAIN Specification Version 1.9 の 5-111 〜 5-114 ページでは、DS によるイベントの処理と、DS からアプリケーションに通知を送るための手法について説明しています。通常、DS は TW_EVENT 構造体を使って、その通知をアプリケーションに送信します。もっと具体的にいうと、DS はその仕様書の 5-113 ページに書かれているように、TW_EVENT.TWMessage フィールドに MSG_XFERREADYMSG_CLOSEDSREQMSG_CLOSEDSOK、または MSG_DEVICEEVENT のいずれかを配置します。

先頭に戻る



DS からクライアントへの情報の受け渡し――新しい方法

情報を(MSG_XFERREADYMSG_CLOSEDSREQ を使うときのように)クライアントアプリケーションに戻すには、TWAIN DS は現在のところ次のように DSM_Entry を通じてコールバックメカニズムを使用する必要があります。



リスト 6. クライアントアプリケーションのコールバックメカニズム

     TW_CALLBACK   callback = {};

     callback.Message = MSG_CLOSEDSREQ;
     result = DSM_Entry((pTW_IDENTITY)&Identity,
                         (pTW_IDENTITY)NULL,
                         (TW_UINT32)DG_CONTROL,
                         (TW_UINT16)DAT_CALLBACK,
                         (TW_UINT16)MSG_INVOKE_CALLBACK,
                         (TW_MEMREF) &callback);



クライアントアプリケーションが古い WaitNextEvent ベースの場合、実際には DSM が処理します。

DSM が callback.Message を取得したら、その次に MSG_PROCESSEVENTEventRecord で呼び出されるときにはメッセージを返します。これにより、WaitNextEvent ベースの Photoshop 7 のようなアプリケーションも新しい TWAIN.framework と新しい形式の TWAIN Data Source を使用することができます。

先頭に戻る



参考資料

TWAIN ワーキンググループの Web サイト

Carbon Event Manager

Mac OS X Bundles

Apple Developer Tools

SampleButtonPlugin サンプルコード

Data Source (DS) サンプルコード